/*

 * Licensed to the Apache Software Foundation (ASF) under one

 * or more contributor license agreements.  See the NOTICE file

 * distributed with this work for additional information

 * regarding copyright ownership.  The ASF licenses this file

 * to you under the Apache License, Version 2.0 (the

 * "License"); you may not use this file except in compliance

 * with the License.  You may obtain a copy of the License at

 *

 *     http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

package com.bff.gaia.unified.sdk.schemas;



import com.bff.gaia.unified.sdk.schemas.annotations.SchemaFieldName;

import com.bff.gaia.unified.sdk.schemas.annotations.SchemaIgnore;

import com.bff.gaia.unified.sdk.schemas.utils.AutoValueUtils;

import com.bff.gaia.unified.sdk.schemas.utils.FieldValueTypeSupplier;

import com.bff.gaia.unified.sdk.schemas.utils.JavaBeanUtils;

import com.bff.gaia.unified.sdk.schemas.utils.ReflectUtils;

import com.bff.gaia.unified.sdk.values.TypeDescriptor;

import com.bff.gaia.unified.vendor.guava.com.google.common.annotations.VisibleForTesting;



import javax.annotation.Nullable;

import java.lang.reflect.Method;

import java.lang.reflect.Modifier;

import java.util.List;

import java.util.stream.Collectors;



/** A {@link SchemaProvider} for AutoValue classes. */

public class AutoValueSchema extends GetterBasedSchemaProvider {

  /** {@link FieldValueTypeSupplier} that's based on AutoValue getters. */

  @VisibleForTesting

  public static class AbstractGetterTypeSupplier implements FieldValueTypeSupplier {

    public static final AbstractGetterTypeSupplier INSTANCE = new AbstractGetterTypeSupplier();



    @Override

    public List<FieldValueTypeInformation> get(Class<?> clazz) {

      // If the generated class is passed in, we want to look at the base class to find the getters.

      Class<?> targetClass = AutoValueUtils.getBaseAutoValueClass(clazz);

      return ReflectUtils.getMethods(targetClass).stream()

          .filter(ReflectUtils::isGetter)

          // All AutoValue getters are marked abstract.

          .filter(m -> Modifier.isAbstract(m.getModifiers()))

          .filter(m -> !Modifier.isPrivate(m.getModifiers()))

          .filter(m -> !Modifier.isProtected(m.getModifiers()))

          .filter(m -> !m.isAnnotationPresent(SchemaIgnore.class))

          .map(FieldValueTypeInformation::forGetter)

          .map(

              t -> {

                SchemaFieldName fieldName = t.getMethod().getAnnotation(SchemaFieldName.class);

                return (fieldName == null) ? t : t.withName(fieldName.value());

              })

          .collect(Collectors.toList());

    }

  }



  @Override

  FieldValueGetterFactory fieldValueGetterFactory() {

    return (Class<?> targetClass, Schema schema) ->

        JavaBeanUtils.getGetters(targetClass, schema, AbstractGetterTypeSupplier.INSTANCE);

  }



  @Override

  FieldValueTypeInformationFactory fieldValueTypeInformationFactory() {

    return (Class<?> targetClass, Schema schema) ->

        JavaBeanUtils.getFieldTypes(targetClass, schema, AbstractGetterTypeSupplier.INSTANCE);

  }



  @Override

  UserTypeCreatorFactory schemaTypeCreatorFactory() {

    return (Class<?> targetClass, Schema schema) -> {

      // If a static method is marked with @SchemaCreate, use that.

      Method annotated = ReflectUtils.getAnnotatedCreateMethod(targetClass);

      if (annotated != null) {

        return JavaBeanUtils.getStaticCreator(

            targetClass, annotated, schema, AbstractGetterTypeSupplier.INSTANCE);

      }



      // Try to find a generated builder class. If one exists, use that to generate a

      // SchemaTypeCreator for creating AutoValue objects.

      SchemaUserTypeCreator creatorFactory =

          AutoValueUtils.getBuilderCreator(

              targetClass, schema, AbstractGetterTypeSupplier.INSTANCE);

      if (creatorFactory != null) {

        return creatorFactory;

      }



      // If there is no builder, there should be a package-private constructor in the generated

      // class. Use that for creating AutoValue objects.

      creatorFactory =

          AutoValueUtils.getConstructorCreator(

              targetClass, schema, AbstractGetterTypeSupplier.INSTANCE);

      if (creatorFactory == null) {

        throw new RuntimeException("Could not find a way to create AutoValue class " + targetClass);

      }



      return creatorFactory;

    };

  }



  @Nullable

  @Override

  public <T> Schema schemaFor(TypeDescriptor<T> typeDescriptor) {

    return JavaBeanUtils.schemaFromJavaBeanClass(

        typeDescriptor.getRawType(), AbstractGetterTypeSupplier.INSTANCE);

  }

}